﻿using System;
using Tao.OpenGl;
using Aeon.Framework.Objects;

namespace Aeon.Framework
{

/// <summary>
/// Scene output management class.
/// 
/// </summary>
public sealed class World
    {
    // --- Inner Classes:
  
    /// <summary>
    /// Array wrapper for fast iteration and selective sorting.
    /// 
    /// </summary>
    private class SpeedList<U,S> where U : class, IKey where S : class, IDraw, U
        {
        // --- Attributes:
        
        private S[] arrayS;             // Sorted Draw array.
        private Int32 sLimitSize;       // Sorted allocated size.
        private Int32 sActualSize;      // Sorted used elements.

        private U[] arrayU;             // Unsorted Key array.
        private Int32 uLimitSize;       // Unsorted allocated size.
        private Int32 uActualSize;      // Unsorted used elements.
        
        // --- Properties:
        
        /// <summary>
        /// Returns element at specified index, or Null.
        /// 
        /// </summary>
        public U this [Int32 index]
            {
            get {
                if (index >= 0 && index < sActualSize+uActualSize)
                    {
                    if (index >= sActualSize) return(arrayU[index-sActualSize]);
                    else return(arrayS[index]);
                    }

                else return(default(U));
                }
            }
        
        /// <summary>
        /// Returns the number of elements in the collection.
        /// 
        /// </summary>
        public Int32 Count { get { return(sActualSize+uActualSize); } }
        
        // --- Constructor:
        
        public SpeedList()
            {

            clean();
            }
        
        // --- Public Methods:
        
        /// <summary>
        /// Returns the element with matching key, or Null.
        /// 
        /// </summary>
        public U Get(String key)
            {
            U obj = default(U);
            
            for(int cnt = 0; cnt < sActualSize; cnt++)
                {
                if (arrayS[cnt].ID == key)
                    {
                    obj = arrayS[cnt];
                    return(obj);
                    }
                }

            for(int cnt = 0; cnt < uActualSize; cnt++)
                {
                if (arrayU[cnt].ID == key)
                    {
                    obj = arrayU[cnt];
                    break;
                    }
                }
            
            return(obj);
            }
        
        /// <summary>
        /// Adds the element in the collection, if unique.
        /// 
        /// </summary>
        public bool Add(U obj)
            {
            if (Get(obj.ID) == default(U))
                {
                if (obj is S)
                    {
                    if (sActualSize+2 > sLimitSize) rise(true);
                    arrayS[sActualSize] = obj as S;
                    sActualSize++;
                    }

                else {
                     if (uActualSize+2 > uLimitSize) rise(false);
                     arrayU[uActualSize] = obj;
                     uActualSize++;
                     }
                
                return(true);
                }
            
            return(false);
            }
        
        /// <summary>
        /// Removes the element with a matching key, if there is one.
        /// 
        /// </summary>    
        public bool Remove(String key)
            {
            bool success = false;
            
            for(int cnt = 0; cnt < sActualSize; cnt++)
                {
                if (arrayS[cnt].ID == key)
                    {
                    for(int next = cnt+1; next < sActualSize; next++)
                        {
                        arrayS[cnt] = arrayS[next];
                        cnt++;
                        }

                    sActualSize--;
                    success = true;
                    break;
                    }
                }

            if (!success)
                {
                for(int cnt = 0; cnt < uActualSize; cnt++)
                    {
                    if (arrayU[cnt].ID == key)
                        {
                        for(int next = cnt+1; next < uActualSize; next++)
                            {
                            arrayU[cnt] = arrayU[next];
                            cnt++;
                            }

                        uActualSize--;
                        success = true;
                        break;
                        }
                    }
                }

            return(success);
            }
        
        /// <summary>
        /// Removes all elements from the collection.
        /// 
        /// </summary>    
        public bool Clear()
            {
            clean();
            
            return(true);
            }
            
        /// <summary>
        /// Guarantees elements are sorted.
        /// 
        /// </summary>
        public void Sort()
            {

            sort();
            }
            
        // --- Private Methods:
        
        private void clean()
            {
            uActualSize = sActualSize = 0;
            uLimitSize = sLimitSize = 10;
            
            arrayS = new S[sLimitSize];
            arrayU = new U[uLimitSize];
            }

        private void rise(bool sorted)
            {
            if (sorted)
                {
                sLimitSize = (int)(sLimitSize*1.5);
            
                S[] tmp = new S[sLimitSize];
                for(int cnt = 0; cnt < sActualSize; cnt++)
                    {
                    tmp[cnt] = arrayS[cnt];
                    }
            
                arrayS = tmp;
                }

            else {
                 uLimitSize = (int)(uLimitSize*1.5);
            
                 U[] tmp = new U[sLimitSize];
                 for(int cnt = 0; cnt < uActualSize; cnt++)
                    {
                    tmp[cnt] = arrayS[cnt];
                    }
            
                 arrayU = tmp;
                 }
            }
            
        private void sort()
            {
            // Insertion sort for small datasets:
            if (sActualSize < 15)
               {
               for (int cnt = 1; cnt < sActualSize; cnt++)
                   {
                   for (int nxt = cnt; nxt > 0 && index(arrayS[nxt-1]) > index(arrayS[nxt]); nxt--)
                       {
                       S tmp = arrayS[nxt];
                       arrayS[nxt] = arrayS[nxt-1];
                       arrayS[nxt-1] = tmp;
                       }
                   }
               }

            // Smoothsort for large datasets:
            else {
                 // [TODO]
                 }
            }

        private int index(S obj)
            {
            // zIndex-fu. [TODO]
            // find, relative to camera angle, z index order of IDraw
            // first pass order: front to back
            // second pass order: back to front

            //obj.Origin.Distance(cameraPos);

            return(0);
            }
            
        }
    
    // --- Attributes:

    private String identifier;                      // World context identifier.

    private SpeedList<IKey,IDraw> actors;           // World actors list.

    private Int32 cameraHor;                        // Display width.
    private Int32 cameraVer;                        // Display height.
    private Position cameraPos;                     // Camera position.
    private Orientation cameraAng;                  // Camera angle.

    private ITextureManager tMan;                   // Context texture manager.
    private IFontManager fMan;                      // Context font manager.    
    private IAudioManager aMan;                     // System audio manager.
    private IInputManager iMan;                     // System input manager.
    private IPhysicsManager pMan;                   // Scene actors physics manager.

    // --- Properties:

    public String Identifier { get { return(identifier); } }

    //public ITextureManager TextureManager { get { return(tMan); } }
    public IFontManager FontManager { get { return(fMan); } }
    public IAudioManager AudioManager { get { return(aMan); } }
    public IInputManager InputManager { get { return(iMan); } }
    public IPhysicsManager PhysicsManager { get { return(pMan); } }
    
    internal ITextureManager InjectTexture { set { tMan = value; } }
    internal IFontManager InjectFont { set { fMan = value; } }
    internal IAudioManager InjectAudio { set { aMan = value; } }
    internal IInputManager InjectInput { set { iMan = value; } }
    internal IPhysicsManager InjectPhysics { set { pMan = value; } }

    // --- Constructor:

    internal World(String identifier)
        {
        this.identifier = identifier;
        
        actors = new SpeedList<IKey,IDraw>();
        
        cameraHor = cameraVer = 0;
        cameraPos = Position.Origin;
        cameraAng = Orientation.Origin;
        }
        
    // --- Object Management:

    /// <summary>
    /// Adds unique object to the world.
    /// Can fail if object does not have an unique identifier.
    /// Can fail if object is a light and the max number of lights is reached.
    /// 
    /// </summary>    
    public bool Add(IKey obj)
        {
        if (actors.Get(obj.ID) == null)
            {
            if (obj is AeonLight)
                {
                AeonLightbulb bulb = new AeonLightbulb(obj as AeonLight);
                if (bulb.TurnOn()) obj = bulb;
                }

            IMaterial m = obj as IMaterial;
            if (m != null) 
                {
                ITexture t = m.Material.Texture;
                t.Observer += tMan.TextureChanged;
                t.Name = t.Name;
                }
                
            return(actors.Add(obj));
            }
            
        return(false);
        }

    /// <summary>
    /// Removes identified object from the world.
    /// Can fail if object identification isn'k found.
    /// 
    /// </summary>    
    public bool Remove(String id)
        {
        IKey k = actors.Get(id);

        AeonLightbulb l = k as AeonLightbulb;
        if (l != null) l.TurnOff();

        IMaterial m = k as IMaterial;
        if (m != null) 
            {
            // [TODO] Leftover texture data?
            m.Material.Texture.Observer -= tMan.TextureChanged;
            tMan.ForgetTexture(m.Material.Texture);
            }
        
        return(actors.Remove(id));
        }
        
    // --- Object Interfaces:

    /// <summary>
    /// Updates identified object position in the world based on an absolute position.
    /// Can fail if object identification isn't found or object is not movable.
    /// 
    /// </summary>    
    public bool MoveTo(String id, Position pos)
        {
        IMove m = actors.Get(id) as IMove;
        
        if (m != null)
           {
           m.MoveTo(pos);
           return(true);
           }
            
        return(false);
        }

    /// <summary>
    /// Updates identified object position in the world based on a relative vector.
    /// Can fail if object identification isn't found or object is not movable.
    /// 
    /// </summary>   
    public bool MoveBy(String id, IVector vec)
        {
        IMove m = actors.Get(id) as IMove;
        
        if (m != null)
           {
           m.MoveBy(vec);
           return(true);
           }
            
        return(false);
        }

    /// <summary>
    /// Updates identified object orientation in the world based on an absolute orientation.
    /// Can fail if object identification isn't found or object is not orientable.
    /// 
    /// </summary>
    public bool TurnTo(String id, Orientation angle)
        {
        ITurn t = actors.Get(id) as ITurn;
        
        if (t != null)
           {
           t.RotateTo(angle);
           return(true);
           }
            
        return(false);
        }

    /// <summary>
    /// Updates identified object orientation in the world based on a relative axis.
    /// Can fail if object identification isn't found or object is not orientable.
    /// 
    /// </summary>    
    public bool TurnBy(String id, Orientation angle)
        {
        ITurn t = actors.Get(id) as ITurn;
        
        if (t != null)
           {
           t.RotateBy(angle);
           return(true);
           }
            
        return(false);
        }
    
    // --- Camera Methods:

    /// <summary>
    /// Resizes the drawing viewport.
    /// 
    /// </summary>    
    public void SetCamera(int height, int width)
        {
        cameraVer = height;
        cameraHor = width;
        }

    /// <summary>
    /// Moves the camera viewpoint to a new position.
    /// 
    /// </summary>
    public bool CameraMoveTo(Position pos)
        {
        cameraPos.Change(pos);

        return(true);
        }

    /// <summary>
    /// Changes the camera viewpoint with a vector.
    /// 
    /// </summary>
    public bool CameraMoveBy(IVector vec)
        {
        cameraPos.Transform(vec);

        return(true);
        }

    /// <summary>
    /// Moves the camera viewangle to a new orientation.
    /// 
    /// </summary>
    public bool CameraTurnTo(Orientation ang)
        {
        cameraAng.Change(ang);

        return(true);
        }

    /// <summary>
    /// Changes the camera viewangle with orientation rotations.
    /// 
    /// </summary>
    public bool CameraTurnBy(Orientation ang)
        {
        cameraAng.Transform(ang);

        return(true);
        }

    /// <summary>
    /// Controls the view position and direction.
    /// Before rendering executes inverse coordinates.
    /// Requires an active OpenGL context.
    /// 
    /// </summary>
    private void cameraPlace()
        {
        Gl.glMatrixMode(Gl.GL_MODELVIEW);
        Gl.glLoadIdentity();

        Gl.glTranslatef(-cameraPos.xPos,-cameraPos.yPos,-cameraPos.zPos);
        Gl.glRotatef(-cameraAng.xAng,1,0,0);
        Gl.glRotatef(-cameraAng.yAng,0,1,0);
        Gl.glRotatef(-cameraAng.zAng,0,0,1);
        }

    /// <summary>
    /// Controls the view size and distance.
    /// Before rendering sets the projection matrix.
    /// Requires an active OpenGL context.
    /// 
    /// </summary>
    private void cameraSize()
        {
        Gl.glMatrixMode(Gl.GL_PROJECTION);
        Gl.glLoadIdentity();

        Gl.glViewport(0, 0, cameraHor, cameraVer);

        if (cameraVer == 0) cameraVer++;
        Glu.gluPerspective(30,(Double)cameraHor/cameraVer,1,100000);
        }

    // --- OpenGL Methods:

    /// <summary>
    /// Prepares the context for rendering.
    /// Requires an active OpenGL context.
    /// 
    /// </summary>
    internal void CreateScene()
        {
        Gl.glShadeModel(Gl.GL_SMOOTH);
        
        Gl.glEnable(Gl.GL_CULL_FACE);
        Gl.glCullFace(Gl.GL_BACK);
        
        Gl.glEnable(Gl.GL_COLOR_MATERIAL);
        Gl.glColorMaterial(Gl.GL_FRONT,Gl.GL_AMBIENT_AND_DIFFUSE);
        
        Gl.glEnable(Gl.GL_TEXTURE_2D);
        Gl.glTexEnvf(Gl.GL_TEXTURE_ENV,Gl.GL_TEXTURE_ENV_MODE,Gl.GL_MODULATE);

        Gl.glEnable(Gl.GL_DEPTH_TEST);
        Gl.glDepthFunc(Gl.GL_LEQUAL);
                                               
        Gl.glEnable(Gl.GL_BLEND);
        Gl.glBlendFunc(Gl.GL_ONE,Gl.GL_ONE_MINUS_SRC_ALPHA);
        //Gl.glBlendFunc(Gl.GL_SRC_ALPHA,Gl.GL_ONE_MINUS_SRC_ALPHA);
        
        Gl.glHint(Gl.GL_LINE_SMOOTH_HINT, Gl.GL_NICEST);
        Gl.glHint(Gl.GL_PERSPECTIVE_CORRECTION_HINT, Gl.GL_NICEST);

        Gl.glClearDepth(1.0f);
        Gl.glClearColor(AeonColors.Default.R, AeonColors.Default.G, AeonColors.Default.B, AeonColors.Default.A);
        }

    /// <summary>
    /// Clears the scene and resets used resources.
    /// Requires an active OpenGL context.
    /// 
    /// </summary>
    public void DestroyScene()
        {
        for (int cnt = actors.Count-1; cnt >= 0; cnt--) { Remove(actors[cnt].ID); }
        actors.Clear();
        
        tMan.CleanTextures();
        iMan.CleanInputs();
        aMan.CleanSounds();
        fMan.CleanFonts();
        
        // [TODO] Clean all managers.
        }        
        
    /// <summary>
    /// Renders all member objects on a screen frame.
    /// Requires an active OpenGL context.
    /// 
    /// </summary>
    public void RenderScene()
        {
        Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);   
        
        // Execute stored inputs (contextualized) [TODO] 
        // Execute and log stored commands (moves, turns) [TODO]

        cameraSize();
        cameraPlace();
        
        actors.Sort();
        
        //for (int cnt = actors.Count-1; cnt >= 0; cnt--)   [TODO] Multipass? IShadow? s.Cast()?
        for (int cnt = 0; cnt < actors.Count; cnt++)
            {
            IDraw d  = actors[cnt] as IDraw;
            if (d as IDraw != null)
                {
                IMaterial m = d as IMaterial;
                if (m != null) m.Material.Apply();
                
                Gl.glPushMatrix();
                d.Draw();
                Gl.glPopMatrix();

                AeonTexture.None.Bind();
                }
            }

        Gl.glFlush();
        }

    }
    

}